package com.sneakxy.mybatis.commons.repository.query.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import com.sneakxy.mybatis.commons.repository.query.domain.criterions.AbstractCriterion;
import com.sneakxy.mybatis.commons.repository.query.domain.criterions.BetweenCriterion;
import com.sneakxy.mybatis.commons.repository.query.domain.criterions.CompareCriterion;
import com.sneakxy.mybatis.commons.repository.query.domain.criterions.EqCriterion;
import com.sneakxy.mybatis.commons.repository.query.domain.criterions.InableCriterion;
import com.sneakxy.mybatis.commons.repository.query.domain.criterions.NotEqCriterion;
import com.sneakxy.mybatis.commons.repository.query.domain.criterions.NullableCriterion;
import com.sneakxy.mybatis.commons.repository.query.domain.criterions.RegexCriterion;

public class LambdaCriteriaBuilder<T> {

	public static final String OR = "OR";
	
	public static final String AND = "AND";
	
	private String logic;
	
	private List<Condition> conditions = new ArrayList<Condition>();
	
	private LambdaCriterion<T> lambdaCriterion;
	
	public LambdaCriteriaBuilder(Class<T> loader) {
		this.lambdaCriterion = new LambdaCriterion<>(this, loader);
	}
	
	public LambdaCriterion<T> or() {
		setLogic(OR);
		return lambdaCriterion;
	}

	public LambdaCriterion<T> and() {
		setLogic(AND);
		return lambdaCriterion;
	}
	
	public LambdaCriteriaBuilder<T> or(Consumer<LambdaCriterion<T>> consumer) {
		setLogic(OR);
		final LambdaCriteriaBuilder<T> builder = new LambdaCriteriaBuilder<>(lambdaCriterion.getLoader());
		consumer.accept(builder.or());
		merge(builder);
		return this;
	}
	
	public LambdaCriteriaBuilder<T> and(Consumer<LambdaCriterion<T>> consumer) {
		setLogic(AND);
		final LambdaCriteriaBuilder<T> builder = new LambdaCriteriaBuilder<>(lambdaCriterion.getLoader());
		consumer.accept(builder.and());
		merge(builder);
		return this;
	}
	
	protected void merge(final LambdaCriteriaBuilder<T> builder) {
		int length = builder.getConditions().size();
		if(length > 1) {
			((AbstractCriterion) builder.getConditions().get(0).getCriterion()).setOpen("(");
			((AbstractCriterion) builder.getConditions().get(length - 1).getCriterion()).setClose(")");
		}
		getConditions().addAll(builder.getConditions());
	}
	
	public Criteria<T> build() {
		return new CriteriaImpl<T>(this.getConditions());
	}
	
	protected LambdaCriteriaBuilder<T> addCriterion(Criterion criterion) {
		getConditions().add(new Condition(getLogic(), criterion));
		return this;
	}
	
	protected List<Condition> getConditions() {
		return conditions;
	}

	protected String getLogic() {
		return logic;
	}

	protected void setLogic(String logic) {
		this.logic = logic;
	}

	public class LambdaCriterion<V> {
		
		private Class<V> loader;
		
		private LambdaCriteriaBuilder<V> lambdaCriteriaBuilder;
		
		protected LambdaCriterion(LambdaCriteriaBuilder<V> lambdaCriteriaBuilder, Class<V> loader) {
			this.lambdaCriteriaBuilder = lambdaCriteriaBuilder;
			this.loader = loader;
		}
		
		public LambdaCriteriaBuilder<V> eq(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new EqCriterion(getLoader(), property, value));
		}
		
		public LambdaCriteriaBuilder<V> eq(String property, Object value, boolean ignoreCase) {
			return getLambdaCriteriaBuilder().addCriterion(new EqCriterion(getLoader(), property, value, ignoreCase));
		}
		
		public LambdaCriteriaBuilder<V> notEq(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new NotEqCriterion(getLoader(), property, value));
		}
		
		public LambdaCriteriaBuilder<V> notEq(String property, Object value, boolean ignoreCase) {
			return getLambdaCriteriaBuilder().addCriterion(new NotEqCriterion(getLoader(), property, value, ignoreCase));
		}
		
		public LambdaCriteriaBuilder<V> between(String property, Object startValue, Object endValue) {
			return getLambdaCriteriaBuilder().addCriterion(new BetweenCriterion(getLoader(), property, startValue, endValue));
		}
		
		public LambdaCriteriaBuilder<V> isNull(String property) {
			return getLambdaCriteriaBuilder().addCriterion(new NullableCriterion(getLoader(), property, true));
		}
		
		public LambdaCriteriaBuilder<V> isNotNull(String property) {
			return getLambdaCriteriaBuilder().addCriterion(new NullableCriterion(getLoader(), property, false));
		}
		
		public LambdaCriteriaBuilder<V> lt(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new CompareCriterion(getLoader(), property, value, "<"));
		}
		
		public LambdaCriteriaBuilder<V> lte(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new CompareCriterion(getLoader(), property, value, "<="));
		}
		
		public LambdaCriteriaBuilder<V> gt(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new CompareCriterion(getLoader(), property, value, ">"));
		}
		
		public LambdaCriteriaBuilder<V> gte(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new CompareCriterion(getLoader(), property, value, ">="));
		}
		
		public LambdaCriteriaBuilder<V> like(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new CompareCriterion(getLoader(), property, value, "LIKE"));
		}
		
		public LambdaCriteriaBuilder<V> like(String property, Object value, boolean ignoreCase) {
			return getLambdaCriteriaBuilder().addCriterion(new CompareCriterion(getLoader(), property, value, "LIKE", ignoreCase));
		}
		
		public LambdaCriteriaBuilder<V> notlike(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new CompareCriterion(getLoader(), property, value, "NOT LIKE"));
		}
		
		public LambdaCriteriaBuilder<V> notlike(String property, Object value, boolean ignoreCase) {
			return getLambdaCriteriaBuilder().addCriterion(new CompareCriterion(getLoader(), property, value, "NOT LIKE", ignoreCase));
		}
		
		public LambdaCriteriaBuilder<V> in(String property, Iterable<?> value) {
			return getLambdaCriteriaBuilder().addCriterion(new InableCriterion(getLoader(), property, value, true));
		}
		
		public LambdaCriteriaBuilder<V> notIn(String property, Iterable<?> value) {
			return getLambdaCriteriaBuilder().addCriterion(new InableCriterion(getLoader(), property, value, false));
		}
		
		public LambdaCriteriaBuilder<V> regex(String property, Object value) {
			return getLambdaCriteriaBuilder().addCriterion(new RegexCriterion(getLoader(), property, value));
		}
		
		public LambdaCriteriaBuilder<V> getLambdaCriteriaBuilder() {
			return lambdaCriteriaBuilder;
		}

		public Class<V> getLoader() {
			return loader;
		}
		
	}
	
}
